package cn.tedu.charging.device.dao.repository;

import cn.tedu.charging.device.pojo.po.StationPO;
import lombok.extern.slf4j.Slf4j;
import org.springframework.data.geo.*;
import org.springframework.data.redis.connection.RedisCommands;
import org.springframework.data.redis.connection.RedisGeoCommands;
import org.springframework.data.redis.core.GeoOperations;
import org.springframework.data.redis.core.RedisTemplate;

import java.math.BigDecimal;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

@Slf4j
public abstract class AbstractStationCacheRepository {

    /**
     * 定义抽象方法
     * 子类来实现
     * @return
     */
    public abstract RedisTemplate getRedisTemplate();

    /**
     * 通过Redis GEO 类型保存场站的位置信息
     * Member 是 场站的id   场站的id 和 String 类型的基本信息 做关联
     *
     * 查询附近充电站
     * 1 先通过用户的 位置和范围 查出 充电站id 列表
     * 2 通过 id 去  基本信息 字符串类型的 查场站信息
     * @param stationPOs
     */
    protected void saveLocationByGEOAndMemberIsId(List<StationPO> stationPOs) {

        GeoOperations geoOperations = getRedisTemplate().opsForGeo();

        //Map<M, Point> memberCoordinateMap
        //充电站id 是 member ， Point member对应的点
        Map<Integer, Point> map = new HashMap<>();

        for (StationPO stationPO : stationPOs) {
            //获取经度
            BigDecimal stationLng = stationPO.getStationLng();
            //获取维度
            BigDecimal stationLat = stationPO.getStationLat();

            //BigDecimal 通过toEngineeringString 转换为字符串
            String stringLng = stationLng.toEngineeringString();
            String stringLat = stationLat.toEngineeringString();

            //Double.valueOf 方法 把字符串 转换为 Double类型
            Double doubleLng = Double.valueOf(stringLng);
            Double doubleLat = Double.valueOf(stringLat);

            //通过double 类型的经纬度 创建 Spring-data-redis 对象 Point
            Point point = new Point(doubleLng, doubleLat);

            //把 stationPO.getId() 充电站id member 和 point member对应的点 添加到 map 中
            map.put(stationPO.getId(),point);
        }
        geoOperations.add("stations_mapping",map);
    }


    public List<StationPO> search(Double lng, Double lat, Double radius) {

        GeoOperations geoOperations = getRedisTemplate().opsForGeo();

        Circle circle = getCircle(lng, lat, radius);

        GeoResults<RedisCommands.GeoLocation<Integer>>
                results = geoOperations.radius("stations_mapping", circle);

        List<StationPO> stationPOS = new ArrayList<>();
        //先通过用户的 位置和范围 查出 充电站id 列表

        for (GeoResult<RedisGeoCommands.GeoLocation<Integer>> result : results) {
            Distance distance = result.getDistance();
            log.debug("距离:{}",distance);
            //GeoResult 里的具体的数据 用 content
            RedisGeoCommands.GeoLocation<Integer> content =
                    result.getContent();
            log.debug("content:{}",content);
            //name 表示的是 GEOADD  是 member stationId
            Integer stationId = content.getName();
            //通过场站id 从查询场站信息
            //不同的存储类型 查询的方式是不一样的 所以子类来实现
            StationPO stationPO = getStationInfoById(String.valueOf(stationId));
            stationPOS.add(stationPO);
        }
        return stationPOS;
    }

    /**
     * 定义抽象方法
     * 子类来实现
     * String 用 opsForValue
     * Hash 用 opsForHash
     * @return
     */
    public abstract StationPO getStationInfoById(String stationId);


    /**
     * 通过 点 和 半径 画个圆
     * 理解 点 半径 圆
     * @param lng
     * @param lat
     * @param radius
     * @return
     */
    protected Circle getCircle(Double lng, Double lat, Double radius){
        //通过经纬度 创建一个 Point 对象
        Point point = new Point(lng,lat);
        //通过距离 设置 圆的半径 同时设置距离的单位 KILOMETERS
        //附近的充电站，附近的酒店 都是距离单位是 KILOMETERS
        //步行导航 距离单位应该是 米
        Distance distance = new Distance(radius, Metrics.KILOMETERS);
        //通过 点 和 半径 画个圆
        Circle circle = new Circle(point,distance);
        return circle;
    }
}
